package com.zyj.mapping;

import com.zyj.annotation.Controller;
import com.zyj.annotation.RequestMapping;
import com.zyj.handler.HandlerMethod;
import com.zyj.spring.aware.BeanFactoryAware;
import com.zyj.spring.factory.BeanFactory;
import com.zyj.spring.factory.support.DefaultListableBeanFactory;
import com.zyj.spring.init.InitializingBean;
import com.zyj.spring.ioc.BeanDefinition;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class RequestMappingHandlerMapping implements HandlerMapping, BeanFactoryAware, InitializingBean {
    private Map<String, HandlerMethod> urlHandlers = new HashMap<>();

    private DefaultListableBeanFactory beanFactory;

    /**
     * 使用注解方式开发与使用xml文件开发springmvc处理器，getHandler的处理逻辑是一样的
     * 区别在于初始化的时候，解析url建立url和处理器的映射关系的逻辑有所不同
     *
     * @param request
     * @return
     */
    @Override
    public Object getHandler(HttpServletRequest request) {
        String uri = request.getRequestURI();
        if (uri == null || "".equals(uri)) {
            return null;
        }
        return this.urlHandlers.get(uri);
    }

    /**
     * BeanFactoryAware接口中的方法
     *
     * @param beanFactory
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = (DefaultListableBeanFactory) beanFactory;
    }

    /**
     * InitializingBean接口中的方法，在当前映射器在容器中创建bean的初始化阶段调用该方法
     * <p>
     * 在DispatcherServlet类初始化的时候会初始化策略，初始化策略的时候会得到所有的映射器和适配器bean，进而在调用getBean方法创建当前映射器bean的时候，在bean的初始化阶段会调用当前方法，建立所有url和处理器的映射关系
     */
    @Override
    public void afterPropertiesSet() {
        List<BeanDefinition> beanDefinitions = beanFactory.getBeanDefinitions();
        // 遍历容器中所有的beanDefinition
        // 所有url和处理器的映射关系都会在该循环中建立
        for (BeanDefinition bd : beanDefinitions) {
            // 获取对应的bean的类型
            Class<?> clazzType = bd.getClazzType();
            // 是否是带有@Controller或者@RequestMapping注解的类
            // 判断当前beanDefinition是否是处理器，如果是处理器，则需要遍历该处理器中所有的方法，获取方法上的url然后建立url和真正处理器之间的映射关系
            if (isHandler(clazzType)) {
                // 获取当前类中的所有方法对象
                Method[] methods = clazzType.getMethods();
                // 继续遍历
                // 遍历所有的方法，这样的话可以使得一个处理器通过拥有多个不同的方法来处理不同的请求，相较于原先xml配置的方式，会好很多，原先xml配置处理器的方式依然使得每个处理器类只能处理单个请求
                // 而通过注解的方式，可以使得一个处理器拥有处理多个不同请求的能力
                // 遍历所有的方法对象，判断每个方法是否被指定注解RequestMapping所修饰，如果被指定注解所修饰，则认为是用来处理请求的方法，需要拼接其上的url
                for (Method method : methods) {
                    // 只有带有@RequestMapping注解的方法，才是我们要解析的方法
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        // 将类上和方法上的url进行合并
                        // 要根据当前类对象和当前方法对象合并两者上通过注解配置的url
                        String url = combine(clazzType, method);
                        // 在这样的逻辑下，一个处理器会与多个url建立映射关系
                        // 将当前controller以及当前方法封装进HandlerMethod对象中，HandlerMethod才是真正与url建立映射关系的处理器，而不是程序员编写的Controller类

                        // 将当前controller的bean对象和当前method对象交给HandlerMethod对象所持有
                        HandlerMethod hm = new HandlerMethod(beanFactory.getBean(bd.getBeanName()), method);

                        // 建立URL和HandlerMethod对象的的映射关系
                        this.urlHandlers.put(url, hm);
                    }
                }
            }
        }

    }

    /**
     * 拼接类注解中的url和方法注解中的url字符串，得到完成的url地址
     * @param clazzType
     * @param method
     * @return
     */
    private String combine(Class<?> clazzType, Method method) {
        // 方法url
        RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
        String methodUrl = methodRequestMapping.value();

        // 类url
        RequestMapping clazzRequestMapping = clazzType.getAnnotation(RequestMapping.class);
        String clazzUrl = null;
        if (clazzRequestMapping != null) {
            // 获取类上RequestMapping注解中的url
            clazzUrl = clazzRequestMapping.value();
        }

        StringBuffer sb = new StringBuffer();

        // 先拼接类上的url
        if (clazzUrl != null && !clazzUrl.equals("")) {
            if (!clazzUrl.startsWith("/")) {
                sb.append("/");
            }
            sb.append(clazzUrl);
        }

        // 再拼接方法上的url
        if (!methodUrl.startsWith("/")) {
            sb.append("/");
        }
        sb.append(methodUrl);

        // 返回拼接结果
        return sb.toString();
    }

    /**
     * 判断当前类是否是处理器
     *
     * @param handler
     * @return
     */
    private boolean isHandler(Class handler) {
        // 如果当前类被Controller注解或者RequestMapping注解修饰，则认为是处理器
        return (handler.isAnnotationPresent(Controller.class)
                || handler.isAnnotationPresent(RequestMapping.class));
    }
}
